package test.link;

import java.io.IOException;

import test.link.DebugRegister.CodePoint;
import link.Constants;
import link.LinkState;
import link.LptConnection;
import link.TimeOutException;
import link.correction.Hamming8_4Corrector;
import link.correction.NotCorrectableException;

public class DebugLPTConnection extends LptConnection {
	
	private DebugRegister dbr = DebugRegister.getInstance();
	
	private long sendrecStartTime;
	
	public DebugLPTConnection(){
		super();
		super.lpt = new DebugLpt();
		lpt.writeLPT(0x00);
		dbr.sendQueue = super.sendQueue;
	}
	
	@Override
	public void run() {
		running = true;
		lpt.writeLPT(0x00);
		while(running){
			if(ls == LinkState.WAITING){
				dbr.setCp(CodePoint.WAITING);
				if(ones(lpt.readLPT()) > 3){ //if the other side is trying to send
					ls = LinkState.RECEIVING;
					byte[] buf = new byte[Constants.MAX_FRAME_SIZE];
					dbr.setCp(CodePoint.BEFORE_REC);
					int length = rec(buf);
					dbr.setCp(CodePoint.AFTER_REC);
					lpt.writeLPT(0x00);
					ls = LinkState.WAITING;
					if(length == -1){ // this packet could not be corrected, ignore it
						System.err.println("Uncorrectable packet");
						continue;
					} else if(length == -2){ // this packet could not be corrected, ignore it
						System.err.println("Timeout while receiving");
						lpt.writeLPT(0x00);
						waitAfterTimeOut();
						continue;
					}
					byte[] r = new byte[length];
					System.arraycopy(buf, 0, r, 0, length);
					recQueueLock.lock();
					recQueue.add(r);
					recQueueLock.unlock();
				} else if(!sendQueue.isEmpty() && (System.currentTimeMillis() - lastSendTime) > (Constants.SEND_WAIT_TIME + Math.random() * Constants.SEND_WAIT_TIME_RAND)){
					boolean hadTimeOut = false;
					ls = LinkState.SENDING;
					recQueueLock.lock();
					byte [] f = sendQueue.poll();
					recQueueLock.unlock();
					dbr.setCp(CodePoint.BEFORE_SEND);
					try{
						sendFrame(f);
					} catch (TimeOutException e) {
						System.err.println("Timeout while sending");
						hadTimeOut = true;
					}
					dbr.setCp(CodePoint.AFTER_SEND);
					lpt.writeLPT(0x00); //set all zeroes on the cable to indicate we are waiting
					lastSendTime = System.currentTimeMillis();
					ls = LinkState.WAITING;
					if(hadTimeOut)
						waitAfterTimeOut();
				}
			}
		}		
	}
	
	@Override
	protected int rec(byte[] buf) {
		startSendRecTimeout();
		//first handle the startSequence
		while(lpt.readLPT()  != 0x0F){ //right now the startsequence with strobe 1 is on the cable
			if(checkSendRecTimeout()) return -2;
			dbr.setCp(CodePoint.REC_SS1);
			lpt.writeLPT(0xFF); //ack ss with strobe 1
		}
		lpt.writeLPT(0x00); //ack the ss with strobe 0
		dbr.setCp(CodePoint.REC_SS0);
		//now handle the data
		int i = 0; //position to write to in the buffer
		try{
			while(true){
				if(i > Constants.MAX_PAYLOAD_SIZE) {					
					System.err.println("PAKKET TE LANG! - ");
					readRealByte(); //frame is to long, ignore bytes larger then frame
					dbr.setCp(CodePoint.REC_LOOP_GT_MAX);
				} else {
					dbr.setCp(CodePoint.REC_LOOP);
					buf[i] = (byte)readRealByte();
					i++;
				}
			}
		} catch (NotCorrectableException e){
			dbr.setCp(CodePoint.REC_NOT_CORRECTABLE);
			i = 0; //return -1
		} catch (TimeOutException e){
			dbr.setCp(CodePoint.REC_TIMEOUT);
			i = -1; //return -2
		} catch(IOException e){
			//this means the tranmission is over
			dbr.setCp(CodePoint.REC_END_TRANSMISSION);
		} 
		return i-1;
	}
	
	
	protected int readRealByte() throws IOException{
		byte[] cor = new byte[2];
		//we need to read two bytes (4 nibbles) from the LPT
		dbr.setCp(CodePoint.RRB_B1A);
		byte b1a = readNibble(); //rightmost 4 bits
		dbr.setCp(CodePoint.RRB_B1B);
		byte b1b = readNibble(); //leftmost 4 bits
		cor[0] = (byte) ((b1a & 0x0F) | ((b1b << 4) & 0xF0));
		dbr.setCp(CodePoint.RRB_B2A);
		byte b2a = readNibble();
		dbr.setCp(CodePoint.RRB_B2B);
		byte b2b = readNibble();
		cor[1] = (byte) ((b2a & 0x0F) | ((b2b << 4) & 0xF0));
		byte[] res = Hamming8_4Corrector.correct(cor);
		return res[0];
	}
	
	protected byte readNibble() throws IOException{
		byte data = 0x00;
		boolean couldBeStop = false;
		while((lpt.readLPT() & 0x10) != 0x10){ //while strobe == 0
			if(checkSendRecTimeout()) throw new TimeOutException();
			lpt.writeLPT(0x00); //indicate we are ready for the next nibble
			dbr.setCp(CodePoint.RN_S0_WAIT);
			//it could be that this while()-loop is never executed when running this method
		}
		while((lpt.readLPT() & 0x10) == 0x10){ //while strobe == 1	
			if(checkSendRecTimeout()) throw new TimeOutException();
			data = (byte) (lpt.readLPT() & 0x0F);
			lpt.writeLPT(0xFF); //indicate we received the strobe
			dbr.setCp(CodePoint.RN_S1_D);
			couldBeStop = data == 0x0F;
		}
		//strobe is now 0
		data = (byte) (lpt.readLPT() & 0x0F);
		dbr.setCp(CodePoint.RN_S0_D);
		if(couldBeStop){ //make sure we did not just misread the strobe
						// the sender will keep resending while we wait
			try {
				Thread.sleep(Constants.COULD_BE_STOP_SLEEP_TIME);
			} catch (InterruptedException e) {}
			data = (byte) (lpt.readLPT() & 0x0F);
			dbr.setCp(CodePoint.RN_COULD_BE_STOP);
		}
		lpt.writeLPT(0x00); //indicate we have read the data
		if(data == 0x00 && couldBeStop) throw new IOException("End of tranmission");
		return data;
	}
	
	protected void sendFrame(byte[] fr) throws TimeOutException{
		startSendRecTimeout();
		//write the startSequence with probe 1
		//and wait for the ack, this should be all ones as the waiting state
		//requires the output to be all ones
		dbr.setCp(CodePoint.SF_SS1);
		sendAndWait((byte)0xFF, true);
		//write the startSequence with strobe 0, is acked by all zeroes
		dbr.setCp(CodePoint.SF_SS0);
		sendAndWait((byte)0x0F, false);
		//now send all other nibbles
		byte[] nib = toNibbles(fr);
		for(int i=0; i<nib.length; i++){
			dbr.setCp(CodePoint.SF_D1);
			sendAndWait((byte)(nib[i] | 0x10), true); //strobe 1
			dbr.setCp(CodePoint.SF_D0);
			sendAndWait((byte)(nib[i] & 0x0F), false); //strobe 0
		}
		//now send the stop sequence
		dbr.setCp(CodePoint.SF_ST1);
		sendAndWait((byte)0x1F, true);
		dbr.setCp(CodePoint.SF_ST0);
		sendAndWait((byte)0x00, false);
	}
	
	protected void sendAndWait(byte toSend, boolean allOnes) throws TimeOutException{
		int i;
		while(true){
			if(checkSendRecTimeout()) throw new TimeOutException();
			if(allOnes)
				dbr.setCp(CodePoint.SAW_1);
			else
				dbr.setCp(CodePoint.SAW_0);
			lpt.writeLPT(toSend);
			i = ones(lpt.readLPT());
			if(allOnes && i > 3)
				return;
			else if(!allOnes && i < 3)
				return;
		}
	}
	
	/**
	 * checks whether the send/rec timeout has passed
	 * @return true when the timeout has passed, false when there still is time
	 */
	protected boolean checkSendRecTimeout(){
		return System.currentTimeMillis() > (sendrecStartTime + Constants.SEND_REC_TIMEOUT);
	}
	
	/**
	 * starts the sendRecTimeout
	 */
	protected void startSendRecTimeout(){
		this.sendrecStartTime = System.currentTimeMillis();
	}
	
	/**
	 * wait for the required amount of time after a timeout
	 */
	protected void waitAfterTimeOut(){
		try {
			Thread.sleep(Constants.AFTER_TIMEOUT_WAIT);
		} catch (InterruptedException e) { }
		this.lastSendTime = System.currentTimeMillis(); //so we wait a random amount of time before sending again		
	}

}
